package mc.support.web.action.invoke;

import mc.support.web.action.ActionWrapper;
import mc.support.web.action.aware.CommonActionAware;
import com.esotericsoftware.reflectasm.MethodAccess;

import java.io.PrintWriter;
import java.io.Writer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created with IntelliJ IDEA. <br/>
 * Author: Francis Yun    <br/>
 * Date: 2014-04-21  <br/>
 */
public class CommonActionInvoker implements Invoker<CommonActionAware> {

    private static final byte[] lock0 = new byte[0];
    private static final byte[] lock1 = new byte[0];
    private static final byte[] lock2 = new byte[0];

    private Map<Class<?>, Map<String, MethodAccessWrapper>> methodAccessCache
            = new ConcurrentHashMap<Class<?>, Map<String, MethodAccessWrapper>>();
    private Map<String, Class<?>> classCache = new ConcurrentHashMap<String, Class<?>>();

    @Override
    public int getPriority() {
        return ActionPriority.COMMON;
    }

    @Override
    public Class<CommonActionAware> getAwareClass() {
        return CommonActionAware.class;
    }

    @Override
    public ResultWriter getResultWriter() {
        return ResultWriter.text;
    }

    @Override
    public Object invoke(CommonActionAware aware, ActionWrapper action, Map<String, Object> params) throws Exception {
        return invoke( action.getActionClsName(), action.getActionInstance(), action.getMethodName(), params );
    }

    private Object invoke(String className, Object actionInstance, String methodName, Map<String, Object> params) {
        Class<?> actionClazz = getActionClass( className );

        MethodAccessWrapper methodAccess = getMethodAccess( actionClazz, methodName );
        return null != methodAccess ? methodAccess.invoke( actionInstance, params )
                : "can't find action method";
    }

    private Class<?> getActionClass(String className) {
        Class<?> clazz = classCache.get( className );
        if ( null == clazz ) {
            synchronized ( lock0 ) {
                clazz = classCache.get( className );
                if ( null == clazz ) {
                    try {
                        clazz = Class.forName( className );
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException( "can't find Action class: " + className );
                    }
                }
            }
            classCache.put( className, clazz );
        }

        return clazz;
    }

    private MethodAccessWrapper getMethodAccess(Class<?> clazz, String methodName) {
        Map<String, MethodAccessWrapper> methodAccesses = methodAccessCache.get(clazz);

        if ( null == methodAccesses ) { //double check!!
            synchronized ( lock1 ) {
                methodAccesses = methodAccessCache.get(clazz);
                if ( null == methodAccesses ) {
                    methodAccesses = addMethodAccesses( clazz );
                }
            }
        }

        MethodAccessWrapper methodAccessWrapper = methodAccesses.get( methodName );
        if ( null == methodAccessWrapper ) { //double check!
            synchronized ( lock2 ) {
                methodAccessWrapper = methodAccesses.get( methodName );
                if ( null == methodAccessWrapper ) {
                    methodAccessWrapper = addMethodAccess( methodAccesses, clazz, methodName );
                }
            }
        }

        return methodAccessWrapper;
    }

    private Map<String, MethodAccessWrapper> addMethodAccesses(Class<?> clazz) {
        Map<String, MethodAccessWrapper> methodAccesses = new ConcurrentHashMap<String, MethodAccessWrapper>();
        methodAccessCache.put( clazz, methodAccesses );
        return methodAccesses;
    }

    private MethodAccessWrapper addMethodAccess(Map<String, MethodAccessWrapper> methodAccesses,
                                                Class<?> clazz, String methodName) {
        MethodAccess methodAccess = MethodAccess.get( clazz );
        int methodIndex = 0;
        MethodAccessWrapper methodAccessWrapper = null;
        if ( validationMethodName( methodAccess, methodName ) ) {
            methodIndex = methodAccess.getIndex( methodName );

            Class<?>[][] parameterTypes = methodAccess.getParameterTypes();
            if ( parameterTypes[ methodIndex ].length != 1 ||
                    parameterTypes[methodIndex][0] != Map.class ) {
                throw new RuntimeException( clazz.getName() + ": method: " + methodName +
                        "'s parameter Type must be " + Map.class.getName() );
            }

            methodAccessWrapper = new MethodAccessWrapper(methodAccess, methodIndex);
            methodAccesses.put( methodName, methodAccessWrapper );
        }

        return methodAccessWrapper;
    }

    private boolean validationMethodName(MethodAccess methodAccess, String methodName) {
        String[] methodNames = methodAccess.getMethodNames();
        boolean isValid = false;
        for ( int i = 0; i < methodNames.length; i++ ) {
            if ( methodNames[i].equals( methodName  ) ) {
                isValid = true;
                break;
            }
        }
        return isValid;
    }

    private void writeResult(Object result, Writer writer, Map params ) {
        PrintWriter printWriter = null;
        if ( writer instanceof PrintWriter) {
            printWriter = (PrintWriter) writer;
        } else {
            printWriter = new PrintWriter( writer );
        }

        printWriter.print( result.toString() );
        printWriter.flush();
    }

    private static class MethodAccessWrapper {

        private final MethodAccess methodAccess;

        private final int methodIndex;

        MethodAccessWrapper(MethodAccess methodAccess, int methodIndex) {
            this.methodAccess = methodAccess;
            this.methodIndex = methodIndex;
        }

        public Object invoke(Object instance, Map params) {
            return methodAccess.invoke( instance, methodIndex, params );
        }

    }

}
